/*
消息协议定义（数据包）

当前定义为大端序，格式为  size + 头 + routeID + seq + data

2025/10/07
区分数据包类型（普通数据 / 心跳包）；
携带路由信息（指定数据归属的业务模块）（这里算是更新了路由机制）；
支持消息序列号（用于排序、去重或确认）；
确保收发双方对数据的解析一致性。
*/
package packet

import (
	"encoding/binary" // 二进制数据编解码 提供了多字节类型（uint16、uint32 等）与字节切片之间的转换能力，是协议实现的核心依赖
	"errors"
)

/* //* 消息协议定义，实现数据包的序列化和反序列化，支持路由标识、序列号管理和心跳包处理，采用大端序编码 */

// 基础协议规则
const (
	// 包头大小
	HeaderSize = 4 + 1 + 2 + 2 // size(4) + header(1) + route(2) + seq(2)
	// 数据包标志(一个标志代表一个包类型)
	FlagData = 0x0
	// 心跳包标志
	FlagHeartbeat = 0x1
)

// 数据包
type Packet struct {
	Size   uint32 // 包长度
	Header byte   // 包头标志 由前面内容确定
	Route  uint16 // 路由ID 指定数据归属业务模块，例如game = 1，聊天 = 2
	Seq    uint16 // 序列号 用于消息排列（可以是有序的），去重或确认（请求-响应配对）
	Body   []byte // 包体数据 转换二进制 可能用于json，protobuf
}

// 打包消息  将业务参数（路由、序列号、业务数据）打包成二进制数据包
//
//	param：route=路由ID，seq=序列号，data=业务数据（如JSON序列化后的字节切片）
//	return：完整的二进制数据包（包头+包体）
func Pack(route uint16, seq uint16, data []byte) []byte {
	size := uint32(HeaderSize + len(data))
	buf := make([]byte, HeaderSize+len(data))

	// binary在这里需要介绍  binary.BigEndian “网络字节序” 的标准（大端序：高位字节存低地址）

	// 写入size bigEndian 大端序，分段写入buf中，读取的时候也按这个顺序
	binary.BigEndian.PutUint32(buf[0:4], size)
	// 写入header （数据包标志）
	buf[4] = FlagData
	// 写入route
	binary.BigEndian.PutUint16(buf[5:7], route)
	// 写入seq
	binary.BigEndian.PutUint16(buf[7:9], seq)
	// 写入body
	copy(buf[9:], data)
	return buf
}

// 打包心跳包
func PackHeartbeat() []byte {
	buf := make([]byte, HeaderSize)
	binary.BigEndian.PutUint32(buf[0:4], HeaderSize)
	buf[4] = FlagData
	// 心跳包不需要route 和 seq
	return buf
}

// 解包消息 将二进制流解析为Packet结构体
//
//	参数：data=收到的二进制数据
//	返回：解析后的Packet指针，或错误（如数据不完整、格式非法）
func Unpack(data []byte) (*Packet, error) {
	// 前置检查：数据长度是否至少等于包头长度（否则无法解析包头）
	// 这里应该有粘包和拆包问题的解决方案
	if len(data) < HeaderSize {
		return nil, errors.New("invalid packet size")
	}
	pkt := &Packet{}
	pkt.Size = binary.BigEndian.Uint32(data[0:4])
	pkt.Header = data[4]
	// 根据Header类型区分处理 (当前使用switch，go的默认无击穿)
	switch pkt.Header {
	case FlagData:
		// 普通数据包：解析Route和Seq
		pkt.Route = binary.BigEndian.Uint16(data[5:7])
		pkt.Seq = binary.BigEndian.Uint16(data[7:9])
		// 解析包体：若数据长度>包头长度，复制剩余字节作为包体
		if len(data) > HeaderSize {
			pkt.Body = make([]byte, len(data)-HeaderSize)
			copy(pkt.Body, data[HeaderSize:])
		}
	case FlagHeartbeat:
		// 心跳包：无需解析Route和Seq（协议约定心跳包无这些字段）
	default:
		// 非法包类型：返回错误
		return nil, errors.New("invalid packet header")
	}
	return pkt, nil
}
